﻿
Imports System.IO
Imports System.Threading
Imports std
Imports storoomc
Imports storcore

'for any operation, should check whether key or buff is valid in public function,
'and any protected/private function can trust the key/buff are always available.
'then can save a call if the data input is not valid.
Partial Public MustInherit Class storoomOper
    Implements storageInterface

    Public Const maxModifyingMutexCount As Int32 = 32767
    Public Const cleanInterval As Int64 = 8000
    'try to use timer3 instead of a single thread
#If SLICESCLEANERTHREAD Then
    Private slicesCleanerThread As Thread = Nothing
#Else
    Private slicesCleanerTimer3Index As Guid = Guid.Empty
#End If
    Protected Delegate Function sliceHealthStatusChangeDelegate(ByVal slice As storageInterface) As Boolean
    Protected sliceUnhealthHandle As sliceHealthStatusChangeDelegate = Nothing
    Protected sliceHealthHandle As sliceHealthStatusChangeDelegate = Nothing
    Public slices As vector(Of storageInterface) = Nothing
    Private unhealthSlices As vector(Of storageInterface) = Nothing

    Private locks As multilock = Nothing
    Private _jobcount As Int64 = 0
    Private tm As threadModel = Nothing

    Public Function threadModel() As threadModel
        assert(Not tm Is Nothing, "threadModel should not be called before initialize or after close.")
        Return tm
    End Function

    Private Sub lockOperating(ByVal key As String)
        If Not locks Is Nothing Then
            locks.lock(key)
        End If
    End Sub

    Private Sub releaseOperating(ByVal key As String)
        If Not locks Is Nothing Then
            locks.release(key)
        End If
    End Sub

    Public Sub disablelocks()
        locks = Nothing
    End Sub

    Public Function jobcount() As Int64
        Return _jobcount
    End Function

    Private Sub increaseJobCount()
        Interlocked.Increment(_jobcount)
    End Sub

    Private Sub decreaseJobCount()
        Interlocked.Decrement(_jobcount)
    End Sub

    'called by slicehealth/sync in nodes_redundance
    Public Function ishealthSlice(ByVal slice As storageInterface) As Boolean
        If slice Is Nothing Then
            Return False
        Else
            Dim i As Int32
            Dim size As Int64 = unhealthSlices.size()
            For i = 0 To size - 1
                If Object.ReferenceEquals(unhealthSlices(i), slice) Then
                    Exit For
                End If
            Next
            Return i = size
        End If
    End Function

    'called by nodes_redundance
    Protected Sub slicesCleanerImpl()
        Dim i As Int32

        i = 0
        While i < unhealthSlices.size()
            If unhealthSlices(i).health Then
                If Not sliceHealthHandle Is Nothing AndAlso Not sliceHealthHandle(unhealthSlices(i)) Then
                    raiseError("sliceHealthHandle returns false.", errorHandle.errorType.warning)
                    Continue While
                End If
                assert(unhealthSlices.erase(i), "unhealthSlices.erase(i) returns false.")
                cacheUpdater.slicesCleaner()
            Else
                i += 1
            End If
        End While

        'can push_back to slices, stornode/slices will use it
        i = 0
        While i < slices.size()
            If isdebugmode() Then
                If Not slices(i) Is Nothing AndAlso isCloneable(slices(i)) Then
                    assert(False, _
                           "slices(i) is cloneable, will break unhealthSlices.push_back() logic. type " + _
                           slices(i).GetType().FullName())
                End If
            End If
            'do not care about existing unhealth slice, so only check slices(i) if it's a healthSlice in last round
            If ishealthSlice(slices(i)) Then
                If Not slices(i).health() Then
                    If Not sliceUnhealthHandle Is Nothing Then
                        If Not sliceUnhealthHandle(slices(i)) Then
                            raiseError("sliceUnhealthHandle returns false.", errorHandle.errorType.warning)
                            Continue While
                        ElseIf slices(i).health() Then
                            i += 1
                            Continue While
                        End If
                    End If
                    assert(unhealthSlices.push_back(slices(i)), "unhealthSlices.push_back(slices(i)) returns false.")
                    cacheUpdater.slicesCleaner()
                End If
            End If
            i += 1
        End While
    End Sub

#If SLICESCLEANERTHREAD Then
    Private Sub slicesCleaner()
        On Error Resume Next
        'assume all slices are health when start
        'unhealthSlices.push_back(slices)

        While True
            slicesCleanerImpl()
            If unhealthSlices.size = 0 Then
                sleep(cleanInterval << 2)
            Else
                sleep(cleanInterval)
            End If
        End While
    End Sub
#End If

    Public Overridable Shadows Sub start()
        initialCache()
#If SLICESCLEANERTHREAD Then
        slicesCleanerThread = New Thread(AddressOf slicesCleaner)
        slicesCleanerThread.Start()
#Else
        slicesCleanerTimer3Index = timer3.addcall(AddressOf slicesCleanerImpl, cleanInterval)
#End If
    End Sub

    'all _opers are for internal use only, private, return boolean
    'all operImpls do not need to care about input, cache <put/get>, locks, and overridable,
    'it was created for uniqueWrite, do not want to call cacheUpdater in two places.
    'the public functions are using me.slices, while all the protected overridable xxxImpl uses the input slices
    'except for storoomDistrOper_fixpoint / nodes_hash, they always uses me.slices.
    'so both storoomDistrOper_fixpoint & nodes_hash cannot inherit, if need to change the slices selecting logic.

    Public Function modify(ByVal key As String, ByVal buff As bytes) As Boolean Implements storageInterface.modify
        increaseJobCount()
        Dim rtn As Boolean = False
        If key Is Nothing OrElse buff Is Nothing Then
            rtn = False
        Else
            Dim startticks As Int64 = Now.Ticks()
            lockOperating(key)
            Dim slice As storageInterface = Nothing
            rtn = modifyImpl(key, buff, slice, modifySelectSlices(key))
            releaseOperating(key)
            If rtn Then
                cacheUpdater.modify(key, buff, slice)
            End If
            STOROOMOPER_COUNTERS.modify(Now.Ticks() - startticks)
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function status(ByRef allcount As UInt64, ByRef unhealthcount As UInt64) As Boolean _
                                                                                    Implements storageInterface.status
        increaseJobCount()
        allcount = slices.size
        unhealthcount = unhealthSlices.size
        decreaseJobCount()
        Return True
    End Function

    Private Delegate Function sliceCheckDelegate(ByVal slice As storageInterface) As Boolean

    Private Function notfullSlice(ByVal slice As storageInterface) As Boolean
        'check health before full, to save time if the slice is down
        Return ishealthSlice(slice) AndAlso Not slice.full()
    End Function

    Private Shared Function randomSlice(ByVal slices As vector(Of storageInterface), _
                                        ByVal check As sliceCheckDelegate, _
                                        ByRef index As Int64) As storageInterface
        assert(Not check Is Nothing, "check is nothing.")
        assert(Not slices Is Nothing, "slices is nothing.")

        Dim slice As storageInterface
        Dim i As Int64
        Dim randStart As Int64
        Dim found As Boolean = False
        randStart = rnd(0, slices.size(), True)
        i = randStart
        Do
            slice = slices(i)
            If check(slice) Then
                found = True
                Exit Do
            End If

            i += 1
            If i >= slices.size() Then
                i = 0
            End If
        Loop Until i = randStart

        If found Then
            index = i
            Return slice
        Else
            Return Nothing
        End If
    End Function

    Protected Function randomSlice(ByVal slices As vector(Of storageInterface), _
                                   ByRef index As Int64) As storageInterface
        Return randomSlice(slices, AddressOf ishealthSlice, index)
    End Function

    Protected Function randomNotFullSlice(ByVal slices As vector(Of storageInterface), _
                                          ByRef index As Int64) As storageInterface
        Return randomSlice(slices, AddressOf notfullSlice, index)
    End Function

    Private Function _write(ByVal key As String, ByVal buff As bytes) As Boolean
        If writeImpl(key, buff, Nothing, writeSelectSlices(key)) Then
            cacheUpdater.write(key, buff)
            Return True
        Else
            Return False
        End If
    End Function

    Public Function write(ByVal key As String, ByVal buff As bytes) As Boolean Implements storageInterface.write
        increaseJobCount()
        Dim rtn As Boolean = False
        If key Is Nothing OrElse buff Is Nothing Then
            rtn = False
        Else
            Dim startticks As Int64 = Now.Ticks()
            lockOperating(key)
            rtn = _write(key, buff)
            releaseOperating(key)
            STOROOMOPER_COUNTERS.write(Now.Ticks() - startticks)
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function uniqueWrite(ByVal key As String, ByVal buff As bytes) As Boolean _
                                                                          Implements storageInterface.uniqueWrite
        increaseJobCount()
        Dim rtn As Boolean = False
        If key Is Nothing OrElse buff Is Nothing Then
            rtn = False
        Else
            lockOperating(key)
            If seekImpl(key, seekSelectSlices(key)) = ternary.false Then
                rtn = _write(key, buff)
            Else
                rtn = False
            End If
            releaseOperating(key)
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function retire() As Boolean Implements storageInterface.retire
        increaseJobCount()
        Dim rtn As Boolean = False
        Dim startticks As Int64 = Now.Ticks()
        rtn = retireImpl(retireSelectSlices())
        If rtn Then
            cacheUpdater.retire()
        End If
        STOROOMOPER_COUNTERS.retire(Now.Ticks() - startticks)
        decreaseJobCount()
        Return rtn
    End Function

    Public Function empty() As Boolean Implements storageInterface.empty
        increaseJobCount()
        Dim b As Boolean = False
        Dim t As ternary = ternary.unknown
        If cacheGetter.empty(b) Then
            b = True
        Else
            t = emptyImpl(emptySelectSlices())
            If t = ternary.unknown Then
                b = True
            Else
                b = (t = ternary.true)
                cacheUpdater.empty(b)
            End If
        End If
        decreaseJobCount()
        Return b
    End Function

    Public Function health() As Boolean Implements storageInterface.health
        Return Not slices Is Nothing
    End Function

    Private Function _close() As Boolean
#If SLICESCLEANERTHREAD Then
        If Not slicesCleanerThread Is Nothing Then
            slicesCleanerThread.Abort()
            slicesCleanerThread = Nothing
        End If
#Else
        If slicesCleanerTimer3Index <> Guid.Empty Then
            If Not timer3.removecall(slicesCleanerTimer3Index) Then
                raiseError("cannot remove slicesCleanerTimer3Index.", errorHandle.errorType.exclamation)
            Else
                slicesCleanerTimer3Index = Guid.Empty
            End If
        End If
#End If
        If Not syncthread Is Nothing Then
            syncthread.Abort()
            syncthread = Nothing
        End If

        If closeImpl(closeSelectSlices()) Then
            threadModel().stop()
            tm = Nothing
            'by-design, storoom / stornode cannot work after close command, the assert will stop the application.
            Return True
        Else
            Return False
        End If
    End Function

    Public Function close() As Boolean Implements storageInterface.close
        increaseJobCount()
        Dim rtn As Boolean = False
        If _close() Then
            cacheUpdater.close()
            rtn = True
        Else
            rtn = False
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function open() As Boolean Implements storageInterface.open
        increaseJobCount()
        Dim rtn As Boolean = False
        If openImpl(openSelectSlices()) Then
            cacheUpdater.open()
            rtn = True
        Else
            rtn = False
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function seek(ByVal key As String) As Boolean Implements storageInterface.seek
        increaseJobCount()
        Dim rtn As Boolean = False
        If key Is Nothing Then
            rtn = False
        Else
            Dim startticks As Int64 = Now.Ticks()
            Dim t As ternary = ternary.unknown
            If cacheGetter.seek(key, rtn) Then
                STOROOMOPER_COUNTERS.seek(Now.Ticks() - startticks)
            Else
                t = seekImpl(key, seekSelectSlices(key))
                If t = ternary.unknown Then
                    rtn = False
                Else
                    rtn = (t = ternary.true)
                    cacheUpdater.seek(key, rtn)
                    STOROOMOPER_COUNTERS.seek(Now.Ticks() - startticks)
                End If
            End If
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function read(ByVal key As String, ByRef buff As bytes) As Boolean Implements storageInterface.read
        increaseJobCount()
        Dim rtn As Boolean = False
        Dim startticks As Int64 = Now.Ticks()
        If key Is Nothing Then
            rtn = False
        ElseIf cacheGetter.value(key, buff) Then
            STOROOMOPER_COUNTERS.read(Now.Ticks() - startticks)
            rtn = True
        Else
            If readImpl(key, buff, readSelectSlices(key)) Then
                cacheUpdater.read(key, buff)
                STOROOMOPER_COUNTERS.read(Now.Ticks() - startticks)
                rtn = True
            Else
                rtn = False
            End If
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function list(ByRef l As vector(Of String)) As Boolean Implements storageInterface.list
        increaseJobCount()
        Dim startticks As Int64 = Now.Ticks()
        If l Is Nothing Then
            l = New vector(Of String)()
        Else
            l.clear()
        End If
        Dim rtn As Boolean = False
        rtn = listImpl(l, listSelectSlices())
        STOROOMOPER_COUNTERS.list(Now.Ticks() - startticks)
        decreaseJobCount()
        Return rtn
    End Function

    Public Function delete(ByVal key As String) As Boolean Implements storageInterface.delete
        increaseJobCount()
        Dim rtn As Boolean = False
        Dim startticks As Int64 = Now.Ticks()
        Dim seekRtn As Boolean = False
        If key Is Nothing Then
            rtn = False
        ElseIf cacheGetter.seek(key, seekRtn) AndAlso Not seekRtn Then
            STOROOMOPER_COUNTERS.delete(Now.Ticks() - startticks)
            rtn = False
        Else
            lockOperating(key)
            rtn = deleteImpl(key, deleteSelectSlices(key))
            releaseOperating(key)
            If rtn Then
                cacheUpdater.delete(key)
            End If
            STOROOMOPER_COUNTERS.delete(Now.Ticks() - startticks)
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function full() As Boolean Implements storageInterface.full
        increaseJobCount()
        Dim b As Boolean = False
        Dim t As ternary = ternary.unknown
        If Not cacheGetter.full(b) Then
            t = fullImpl(fullSelectSlices())
            If t = ternary.unknown Then
                b = True
            Else
                b = (t = ternary.true)
                cacheUpdater.full(b)
            End If
        End If
        decreaseJobCount()
        Return b
    End Function

    Public Function capacity(ByRef cap As UInt64) As Boolean Implements storageInterface.capacity
        increaseJobCount()
        Dim rtn As Boolean = False
        If cacheGetter.capacity(cap) Then
            rtn = True
        Else
            If capacityImpl(cap, capacitySelectSlices()) Then
                cacheUpdater.capacity(cap)
                rtn = True
            Else
                rtn = False
            End If
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function valuesize(ByRef sz As UInt64) As Boolean Implements storageInterface.valuesize
        increaseJobCount()
        Dim rtn As Boolean = False
        If cacheGetter.valuesize(sz) Then
            rtn = True
        Else
            If valuesizeImpl(sz, valuesizeSelectSlices()) Then
                cacheUpdater.valuesize(sz)
                rtn = True
            Else
                rtn = False
            End If
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function sizeof(ByVal key As String, ByRef size As UInt64) As Boolean Implements storageInterface.sizeof
        increaseJobCount()
        Dim rtn As Boolean = False
        Dim startticks As Int64 = Now.Ticks()
        If key Is Nothing Then
            rtn = False
        ElseIf cacheGetter.sizeof(key, size) Then
            STOROOMOPER_COUNTERS.sizeof(Now.Ticks() - startticks)
            rtn = True
        Else
            lockOperating(key)
            rtn = sizeofImpl(key, size, sizeofSelectSlices())
            releaseOperating(key)
            If rtn Then
                cacheUpdater.sizeof(key, size)
            End If
            STOROOMOPER_COUNTERS.sizeof(Now.Ticks() - startticks)
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Public Function keycount(ByRef count As UInt64) As Boolean Implements storageInterface.keycount
        increaseJobCount()
        Dim rtn As Boolean = False
        If cacheGetter.keycount(count) Then
            rtn = True
        Else
            If keycountImpl(count, keycountSelectSlices()) Then
                cacheUpdater.keycount(count)
                rtn = True
            Else
                rtn = False
            End If
        End If
        decreaseJobCount()
        Return rtn
    End Function

    Private syncthread As Thread = Nothing

    Public Function issyncing() As Boolean
        Return _synchelper.issyncing(syncthread)
    End Function

    Public Function sync(ByVal host As String, ByVal port As Int32) As Boolean Implements storageInterface.sync
        'when call sync, the destination server can work now, so do not need to care about the increase of slices.
        'or at least, any newer slice/data cannot be handled here.
        increaseJobCount()
        Dim rtn As Boolean = False
        rtn = issyncing() OrElse synchelper(AddressOf syncImpl, syncSelectSlices(), host, port, syncthread)
        decreaseJobCount()
        Return rtn
    End Function

    Public Sub New()
        slices = New vector(Of storageInterface)
        unhealthSlices = New vector(Of storageInterface)
        locks = New multilock(maxModifyingMutexCount)
        cacheGetter = New cacheGetterDef()
        cacheUpdater = New cacheUpdaterDef()
        tm = resolver.global.resolve(Of threadModel)()
        assert(Not tm Is Nothing, "register threadModel into resolver.global.")
    End Sub

    Private Function d(ByVal _d As _do(Of storageInterface, ternary),
                       ByVal unhealthValue As ternary) As _do(Of storageInterface, ternary)
        assert(Not _d Is Nothing)
        assert(Not unhealthValue Is Nothing)
        Return Function(ByRef s As storageInterface) As ternary
                   If ishealthSlice(s) Then
                       Return _d(s)
                   Else
                       Return unhealthValue
                   End If
               End Function
    End Function

    Protected Function d(ByVal _d As _do(Of storageInterface, ternary)) As _do(Of storageInterface, ternary)
        Return d(_d, ternary.unknown)
    End Function

    Protected Function d_strict(ByVal _d As _do(Of storageInterface, ternary)) As _do(Of storageInterface, ternary)
        Return d(_d, ternary.false)
    End Function

    Private Function d(Of ROUND_RESULT_T) _
                      (ByVal _d As _do(Of storageInterface, ROUND_RESULT_T, ternary),
                       ByVal unhealthValue As ternary) _
                      As _do(Of storageInterface, ROUND_RESULT_T, ternary)
        assert(Not _d Is Nothing)
        assert(Not unhealthValue Is Nothing)
        Return Function(ByRef s As storageInterface, ByRef this As ROUND_RESULT_T) As ternary
                   If ishealthSlice(s) Then
                       Return _d(s, this)
                   Else
                       Return unhealthValue
                   End If
               End Function
    End Function

    Protected Function d(Of ROUND_RESULT_T) _
                        (ByVal _d As _do(Of storageInterface, ROUND_RESULT_T, ternary)) _
                        As _do(Of storageInterface, ROUND_RESULT_T, ternary)
        Return d(_d, ternary.unknown)
    End Function

    Protected Function d_strict(Of ROUND_RESULT_T) _
                               (ByVal _d As _do(Of storageInterface, ROUND_RESULT_T, ternary)) _
                               As _do(Of storageInterface, ROUND_RESULT_T, ternary)
        Return d(_d, ternary.false)
    End Function
End Class
